# gozerbot/persist.py
#
#

""" allow data to be written to disk in JSON format. creating the persisted 
    object restores data.
"""

__copyright__ = 'this file is in the public domain'

# gozerbot imports
from gozerbot.stats import stats
from gozerbot.utils.locking import lockdec
from gozerbot.utils.log  import rlog
from gozerbot.utils.trace import calledfrom
from gozerbot.utils.lazydict import LazyDict
from gozerbot.datadir import datadir
from gozerbot.config import config
from simplejson import load, dump, loads, dumps

# basic imports
import pickle, thread, os, copy, sys, types

saving = []
stopsave = 0

persistlock = thread.allocate_lock()
persistlocked = lockdec(persistlock)

if True:

    class Persist(object):

        """ persist data attribute to JSON file. """

        def __init__(self, filename, default=None, init=True):

            """ Persist constructor """

            self.fn = filename # filename to save to
            self.lock = thread.allocate_lock() # lock used when saving)
            self.data = LazyDict() # attribute to hold the data

            if init:

                if default == None:
                    default = LazyDict()

                self.init(default)

        @persistlocked
        def init(self, default={}):

            """ initialize the data. """

            rlog(0, 'persist', 'reading %s' % self.fn)

            # see if file exists .. if not initialize data to default
            try:
                datafile = open(self.fn, 'r')

            except IOError, ex:

                if not 'No such file' in str(ex):
                    rlog(10, 'persist', 'failed to read %s: %s' % (self.fn, str(ex)))

                    self.data = copy.deepcopy(default)
                    raise
                else:
                    return

            # load the JSON data into attribute
            try:
                self.data = load(datafile)
                datafile.close()
                stats.up('persist', 'load') 

                if type(self.data) == types.DictType:

                    d = LazyDict()
                    d.update(self.data)
                    self.data = d

            except Exception, ex:
                rlog(100, 'persist', 'ERROR: %s' % self.fn)
                raise

        @persistlocked
        def save(self):

            """ persist data attribute. """

            if stopsave:
                rlog(100, 'persist', 'stopping mode .. not saving %s' % self.fn)
                return

            # save data
            try:
                #self.lock.acquire()
                saving.append(str(self.fn))
                tmp = self.fn + '.tmp' # tmp file to save to

                # first save to temp file and when done rename
                try:
                    datafile = open(tmp, 'w')

                except IOError, ex:
                    rlog(100, 'persist', "can't save %s: %s" % (self.fn, str(ex)))
                    return

                # dump JSON to file
                #cp = copy.copy(self.data)
                dump(self.data, datafile)
                datafile.close()

                try:
                    os.rename(tmp, self.fn)

                except WindowsError:
                    # no atomic operation supported on windows! error is thrown when destination exists
                    os.remove(self.fn)
                    os.rename(tmp, self.fn)

                stats.up('persist', 'saved')
                rlog(10, 'persist', '%s saved' % self.fn)

            finally:
                saving.remove(self.fn)
                #self.lock.release()


class PlugPersist(Persist):

    """ persist plug related data. """

    def __init__(self, filename, default=None):

        # retrieve plugname where object is constructed
        plugname = calledfrom(sys._getframe())

        # call base constructor with appropiate filename
        Persist.__init__(self, datadir + os.sep + 'plugs' + os.sep + plugname + os.sep + filename, default)


class LazyDictPersist(Persist):

    """ persisted lazy dict. """

    def __init__(self, default={}):

        # called parent constructor
        Persist.__init__(self)

        # if data not initialised set it to a LazyDict
        if not self.data:
            self.data = LazyDict(default)
